/*
 *  Copyright 2019-2020 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.alien.admin.modules.publisher.service.impl;

import com.alien.admin.exception.BadRequestException;
import com.alien.admin.modules.deploy.service.DeployService;
import com.alien.admin.modules.publisher.domain.PublishProject;
import com.alien.admin.modules.publisher.domain.PublishSheetApprovalRecord;
import com.alien.admin.modules.publisher.domain.PublishSheetFlow;
import com.alien.admin.modules.publisher.repository.PublishProjectRepository;
import com.alien.admin.modules.publisher.repository.PublishSheetFlowRepository;
import com.alien.admin.modules.publisher.service.PublishProjectService;
import com.alien.admin.modules.publisher.service.PublishSheetApprovalRecordService;
import com.alien.admin.modules.publisher.service.PublishSheetFlowService;
import com.alien.admin.modules.publisher.service.dto.*;
import com.alien.admin.modules.publisher.service.mapstruct.PublishSheetFlowMapper;
import com.alien.admin.modules.system.service.UserService;
import com.alien.admin.modules.system.service.dto.JobSmallDto;
import com.alien.admin.modules.system.service.dto.UserDto;
import com.alien.admin.modules.utils.MyDateUtils;
import com.alien.admin.utils.*;
import com.alien.admin.utils.enums.FlowHandleEnum;
import com.alien.admin.utils.enums.FlowNodeIdEnum;
import com.alien.admin.utils.enums.FlowStatusEnum;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zengzhifang
 * @website https://el-admin.vip
 * @description 服务实现
 * @date 2021-10-18
 **/
@Service
public class PublishSheetFlowServiceImpl implements PublishSheetFlowService {

    @Autowired
    private PublishSheetFlowRepository publishSheetFlowRepository;
    @Autowired
    private PublishSheetFlowMapper publishSheetFlowMapper;
    @Autowired
    private PublishSheetApprovalRecordService publishSheetApprovalRecordService;
    @Autowired
    private UserService userService;
    @Autowired
    private PublishProjectRepository publishProjectRepository;
    @Autowired
    private PublishProjectService publishProjectService;
    @Autowired
    private DeployService deployService;

    @Override
    public Map<String, Object> queryAll(PublishSheetFlowQueryCriteria criteria, Pageable pageable) {
        Page<PublishSheetFlow> page = publishSheetFlowRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
        return PageUtil.toPage(page.map(publishSheetFlowMapper::toDto));
    }

    @Override
    public List<PublishSheetFlowDto> queryAll(PublishSheetFlowQueryCriteria criteria) {
        return publishSheetFlowMapper.toDto(publishSheetFlowRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));
    }

    @Override
    public PublishSheetFlowDto findById(Long id) {
        PublishSheetFlow publishSheetFlow = publishSheetFlowRepository.findById(id).orElseGet(PublishSheetFlow::new);
        ValidationUtil.isNull(publishSheetFlow.getId(), "PublishSheetFlow", "id", id);

        PublishSheetFlowDto publishSheetFlowDto = publishSheetFlowMapper.toDto(publishSheetFlow);

        // 获取申请人姓名和岗位
        UserDto userDto = userService.findById(publishSheetFlow.getApplierId());
        publishSheetFlowDto.setApplierName(userDto.getNickName());
        Set<JobSmallDto> jobs = userDto.getJobs();
        if (!jobs.isEmpty()) {
            String jobName = jobs.iterator().next().getName();
            publishSheetFlowDto.setJobName(jobName);
        }

        return publishSheetFlowDto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PublishSheetFlowDto create(PublishSheetFlow resources) {
        // 前端没有传处理人，但是，我们也可以通过流程节点来推断出来
        Long processorId = PublishSheetFlowUtils.getProcessorIdByPublishSheetFlow(resources);
        resources.setProcesserId(processorId);

        Set<PublishProject> publishProjects = resources.getPublishProjects();
        resources.setPublishProjects(null);
        resources.setCreateTime(new Timestamp(System.currentTimeMillis()));

        Long flowId = resources.getId();
        if (flowId != null) {
            PublishProjectQueryCriteria publishProjectCnd = new PublishProjectQueryCriteria();
            publishProjectCnd.setFlowId(flowId);
            List<PublishProjectDto> publishProjectDtoList = publishProjectService.queryAll(publishProjectCnd);
            if (publishProjectDtoList != null && !publishProjectDtoList.isEmpty()) {
                List<Long> ids = publishProjectDtoList.stream().map(item -> item.getId()).collect(Collectors.toList());
                Long[] idsArray = new Long[ids.size()];
                ids.toArray(idsArray);

                publishProjectService.deleteAll(idsArray);
            }
        }
        PublishSheetFlowDto publishSheetFlowDto = publishSheetFlowMapper.toDto(publishSheetFlowRepository.save(resources));

        publishProjects.forEach(publishProject -> {
            publishProject.setId(null);
            publishProject.setFlowId(publishSheetFlowDto.getId());
        });
        publishProjectRepository.saveAll(publishProjects);
        // 如果不是草稿，是提交，那么要生成一个记录
        Boolean isDraft = resources.getIsDraft();
        if (isDraft != null && !resources.getIsDraft()) {
            processApprovalRecord(publishSheetFlowDto);
        }

        return publishSheetFlowDto;
    }

    public void processApprovalRecord(PublishSheetFlowDto publishSheetFlowDto) {
        PublishSheetApprovalRecord approvalRecord = new PublishSheetApprovalRecord();
        approvalRecord.setPublishSheetFlowId(publishSheetFlowDto.getId());
        approvalRecord.setFlowNodeId(publishSheetFlowDto.getFlowNodeId());

        approvalRecord.setCreateTime(new Timestamp(System.currentTimeMillis()));
        approvalRecord.setOperatorId(publishSheetFlowDto.getApplierId());

        UserDto userDto = userService.findById(publishSheetFlowDto.getApplierId());
        approvalRecord.setOperator(userDto.getNickName());

        // 草稿/中止
        Integer flowStatus = publishSheetFlowDto.getFlowStatus();
        if (flowStatus.equals(FlowStatusEnum.Terminate.getId())) {
            approvalRecord.setHandle(FlowHandleEnum.DISCARD.getId());
            approvalRecord.setOpinions("申请者作废");
        } else {
            approvalRecord.setHandle(FlowHandleEnum.Begin.getId());
            approvalRecord.setOpinions("申请者正式提交");
        }

        publishSheetApprovalRecordService.create(approvalRecord);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(PublishSheetFlow resources) {
        PublishSheetFlow publishSheetFlow = publishSheetFlowRepository.findById(resources.getId()).orElseGet(PublishSheetFlow::new);
        ValidationUtil.isNull(publishSheetFlow.getId(), "PublishSheetFlow", "id", resources.getId());
        publishSheetFlow.copy(resources);
        publishSheetFlowRepository.save(publishSheetFlow);
    }

    @Override
    public void deleteAll(Long[] ids) {
        for (Long id : ids) {
            publishSheetFlowRepository.deleteById(id);
        }
    }

    @Override
    public void download(List<PublishSheetFlowDto> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (PublishSheetFlowDto publishSheetFlow : all) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("标题，格式如（服务[ztjy-file-serve#ztjy-upload]发布申请-某某人）", publishSheetFlow.getTitle());
            map.put("流程编号", publishSheetFlow.getFlowNo());
            map.put("发布时间", publishSheetFlow.getPublishTime());
            map.put("申请人id", publishSheetFlow.getApplierId());
            map.put("发布类型 0-需求发布 1-内部优化 2-修复bug 3-特殊需求", publishSheetFlow.getPublishType());
            map.put("涉及线上库变动（0-否 1-是）", publishSheetFlow.getDbChanged());
            map.put("涉及缓存逻辑变动（0-否 1-是）", publishSheetFlow.getCacheChanged());
            map.put("风险评估", publishSheetFlow.getRiskAssessment());
            map.put("数据库脚本（涉及库变动显示)", publishSheetFlow.getDbScript());
            map.put("回滚脚本（涉及库变动显示)", publishSheetFlow.getRollbackDbScript());
            map.put("统配", publishSheetFlow.getConfigserverScript());
            map.put("备注", publishSheetFlow.getRemarks());
            map.put("测试介入（0-否 1-是）", publishSheetFlow.getHasTester());
            map.put("测试人员（测试介入是必选）", publishSheetFlow.getTesterId());
            map.put("运维人员", publishSheetFlow.getDeployerId());
            map.put("开发经理（角色为开发经理的用户列表）", publishSheetFlow.getManagerId());
            map.put("创建时间", publishSheetFlow.getCreateTime());
            map.put("结束时间", publishSheetFlow.getEndTime());
            map.put("0-起草 1-申请单提交（待主管审核）2-待测试确认 3.待运维RC确认4.待测试人员RC发布确认5.待开发人员RC发布确认  6.待运维PROD确认7.待测试PROD发布确认8.待开发人员发布确认 9.结束", publishSheetFlow.getFlowNodeId());
            map.put("当前节点处理人id", publishSheetFlow.getProcesserId());
            map.put("0-草稿 1-进行中 2.结束 3.驳回", publishSheetFlow.getFlowStatus());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    @Override
    public void deploy(Long flowId) {
        // 获取流程信息
        PublishSheetFlowDto publishSheetFlowDto = findById(flowId);
        Long processerId = publishSheetFlowDto.getProcesserId();
        Long currentUserId = SecurityUtils.getCurrentUserId();
        if (!currentUserId.equals(processerId)) {
            throw new BadRequestException("当前处理人才有权限部署服务申请单里面的服务");
        }

        String env = PublishSheetFlowUtils.getEnvByFlowNode(publishSheetFlowDto.getFlowNodeId());
        deployService.deployByFlow(publishSheetFlowDto, env);
    }

    @Override
    public void rollbackBranchByFlow(Long flowId) {
        // 获取流程信息
        PublishSheetFlowDto publishSheetFlowDto = findById(flowId);

        String env = PublishSheetFlowUtils.getEnvByFlowNode(publishSheetFlowDto.getFlowNodeId());
        deployService.rollbackBranch(publishSheetFlowDto, env);
    }

    @Override
    public void deleteBranchByFlow(Long flowId) {
        PublishSheetFlowDto flowDto = findById(flowId);
        String env = PublishSheetFlowUtils.getEnvByFlowNode(flowDto.getFlowNodeId());

        deployService.deleteBranch(flowDto, env);
    }

    /**
     * 某个用户待处理的记录数量
     *
     * @param userId
     * @return
     */
    @Override
    public int toDealCountByUserId(Long userId) {
        return publishSheetFlowRepository.toDealCountByUserId(userId);
    }

    /**
     * 统计某一天内的服务发布数量
     *
     * @param dateStr "2021-11-13"
     * @return
     */
    @Override
    public int oneDayPublishCount(String dateStr) {
        PublishSheetFlowQueryCriteria criteria = new PublishSheetFlowQueryCriteria();
        criteria.setPublishTime(MyDateUtils.getOneDayTimestampList(dateStr));
        Long count = publishSheetFlowRepository.count(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)));
        return count.intValue();
    }

    @Override
    public Map<String, List<PublishSheetFlowDto>> queryTodayList() {
        PublishSheetFlowQueryCriteria criteria = new PublishSheetFlowQueryCriteria();
        Set<Integer> flowStatus = new HashSet<>();
        flowStatus.add(FlowStatusEnum.Process.getId());
        criteria.setFlowStatus(flowStatus);
        criteria.setFlowNodeId(FlowNodeIdEnum.EnvRcDeployer.getId());

        // 获取rc待部署
        List<PublishSheetFlowDto> rcPublishDeployList
                = queryAll(criteria);

        // 获取prod待部署
        criteria.setFlowNodeId(FlowNodeIdEnum.EnvProdDeployer.getId());
        List<PublishSheetFlowDto> prodPublishDeployList
                = queryAll(criteria);

        // 今天0点
        // Timestamp todayBegin = DateUtil.getTimespan(0);
        // 明天0点
        Timestamp todayEnd = DateUtil.getTimespan(-1);
        rcPublishDeployList = rcPublishDeployList.stream().filter(publishSheetFlow -> {
            Timestamp publishTime = publishSheetFlow.getPublishTime();
            return publishTime.before(todayEnd);
        }).collect(Collectors.toList());

        prodPublishDeployList = prodPublishDeployList.stream().filter(publishSheetFlow -> {
            Timestamp publishTime = publishSheetFlow.getPublishTime();
            return publishTime.before(todayEnd);
        }).collect(Collectors.toList());

        // 获取项目冲突
        handleConflictProject(rcPublishDeployList);
        handleConflictProject(prodPublishDeployList);

        Map<String, List<PublishSheetFlowDto>> publishDeployMap = new HashMap<>(2);

        publishDeployMap.put(EnvConstant.RC, rcPublishDeployList);
        publishDeployMap.put(EnvConstant.PROD, prodPublishDeployList);

        return publishDeployMap;
    }

    List<PublishSheetFlowDto> clonePublishSheetFlowDtoList(List<PublishSheetFlowDto> publishSheetFlowDtoList) {
        List<PublishSheetFlowDto> clonePublishSheetFlowDtoList =
        publishSheetFlowDtoList.stream().map(publishSheetFlowDto -> {
            PublishSheetFlowDto pdto = new PublishSheetFlowDto();
            pdto.setId(publishSheetFlowDto.getId());
            pdto.setApplierId(publishSheetFlowDto.getApplierId());
            pdto.setApplierName(publishSheetFlowDto.getApplierName());
            pdto.setFlowNo(publishSheetFlowDto.getFlowNo());
            pdto.setFlowNodeId(publishSheetFlowDto.getFlowNodeId());
            pdto.setFlowStatus(publishSheetFlowDto.getFlowStatus());
            pdto.setTitle(publishSheetFlowDto.getTitle());
            pdto.setPublishTime(publishSheetFlowDto.getPublishTime());
            pdto.setPublishProjects(publishSheetFlowDto.getPublishProjects());
            return pdto;
        }).collect(Collectors.toList());

        return clonePublishSheetFlowDtoList;
    }

    private void handleConflictProject(List<PublishSheetFlowDto> publishSheetFlowDtoList) {
        // 避免引用死循环
        List<PublishSheetFlowDto> clonePublishSheetFlowDtoList = clonePublishSheetFlowDtoList(publishSheetFlowDtoList);

        // 获取冲突的流程
        for(PublishSheetFlowDto publishSheetFlowDto : publishSheetFlowDtoList) {
            Set<PublishDeployDto> publishDeployDtoSet = publishSheetFlowDto.getPublishDeploys();
            publishDeployDtoSet.forEach(publishDeployDto -> {
                // 清空部署日志，避免栈空间溢出
                publishDeployDto.setDeployRecs(null);
            });

            Set<PublishProjectDto> publishProjectDtoSet = publishSheetFlowDto.getPublishProjects();
            List<Long> projectIdList = publishProjectDtoSet.stream().map(p -> p.getProjectId()).collect(Collectors.toList());
            Long[] projectIdArray = new Long[projectIdList.size()];
            projectIdList.toArray(projectIdArray);
            List<PublishSheetFlowDto> conflictFlowList = queryPublishSheetFlowList(projectIdArray, clonePublishSheetFlowDtoList);
            conflictFlowList = conflictFlowList.stream().filter(conflictFlow -> !conflictFlow.getId().equals(publishSheetFlowDto.getId()))
                    .collect(Collectors.toList());
            publishSheetFlowDto.setConflictPublishSheetFlowList(conflictFlowList);
        }
    }

    @Override
    public List<PublishSheetFlowDto> queryPublishSheetFlowList(Long[] projectIds) {
        // 直接获取全部进行时中的流程，然后进行过滤
        PublishSheetFlowQueryCriteria criteria = new PublishSheetFlowQueryCriteria();
        Set<Integer> flowStatus = new HashSet<>();
        flowStatus.add(FlowStatusEnum.Process.getId());
        criteria.setFlowStatus(flowStatus);

        List<PublishSheetFlowDto> publishSheetFlowDtoList = queryAll(criteria);
        return queryPublishSheetFlowList(projectIds, publishSheetFlowDtoList);
    }

    @Override
    public List<PublishSheetFlowDto> queryPublishSheetFlowListByFlowId(Long flowId) {
        PublishSheetFlowDto flowDto = findById(flowId);
        List<Long> projectIds = flowDto.getPublishProjects().stream()
                .map(publishProjectDto -> publishProjectDto.getProjectId()).collect(Collectors.toList());

        Long[] projectIdArray = new Long[projectIds.size()];
        projectIds.toArray(projectIdArray);
        return queryPublishSheetFlowList(projectIdArray);
    }

    @Override
    public List<PublishSheetFlowDto> queryPublishSheetFlowList(Long[] projectIds, List<PublishSheetFlowDto> publishSheetFlowDtoList) {
        publishSheetFlowDtoList = publishSheetFlowDtoList.stream().filter(publishSheetFlowDto -> {
            Set<PublishProjectDto> publishProjects = publishSheetFlowDto.getPublishProjects();
            Set<Long> publishProjectIds = publishProjects.stream().map(p -> p.getProjectId()).collect(Collectors.toSet());
            for(Long projectId : projectIds) {
                if(publishProjectIds.contains(projectId)) {
                    return true;
                }
            }
            return false;
        }).collect(Collectors.toList());
        return publishSheetFlowDtoList;
    }
}
